package gr.atc.epart.googlewrapper.youtube;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.apache.http.Header;
import org.apache.http.entity.FileEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicHeader;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.google.gdata.util.common.net.UriEncoder;

import gr.atc.epart.HttpConnector;
import gr.atc.epart.googlewrapper.GoogleServiceCallbackType;
import gr.atc.epart.googlewrapper.accounts.GoogleAccountManager;
import gr.atc.epart.googlewrapper.youtube.YouTubeSubscription.SubscriptionType;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

public class YouTube
{
	 private GoogleAccountManager	_manager; 
	 private Activity 				_activity;
	 private Handler 				_ownerCallback;
	 private String  				_authToken;
	 private String 				_developerKey = "AI39si4FsIZNNb2n9MdTbp3Fgv_aSXuu4vmLOFedtOgPNaSLE5u04rdw9BFLeu39Fy5N8GLkUpKIe4M_cawgGUOvxHNry-zTEQ";

	 private String 				_resumableUploadsUrl = "http://uploads.gdata.youtube.com/resumable/feeds/api/users/default/uploads";
	 private String					_videoFilename;
	 
	 public enum ComplainType{Porn,Violence,Hate,Dangerous,Rights,Spam};
	  
	 //
	 // used to receive callbacks from low level asynchronous operations  
	 //
	 private Handler _messageHandler = new Handler() 
	  {
	      public void handleMessage(Message msg) 
	      {  
	          switch(msg.what) 
	          {
	        	  case GoogleServiceCallbackType.AuthTokenReceived:
	        		  // ok
	        		  _authToken = msg.getData().getString("AUTHTOKEN");
	        		  _ownerCallback.sendEmptyMessage(GoogleServiceCallbackType.YouTubeServiceIsReady);
	        		  
	        		  break;
	        		  
	        	  case GoogleServiceCallbackType.UserAuthenticationRequired:
	        		  // ok
	        		  _ownerCallback.sendEmptyMessage(GoogleServiceCallbackType.UserAuthenticationRequired);
	        		  break;

	        	  case GoogleServiceCallbackType.YouTubeVideoFeedsReceived:
	        		  //ok
	        		  String videoFeed = msg.getData().getString("RESPONSE");
	        		  ArrayList<YouTubeVideoEntry> videos=null;

	        		  try
	        		  {
	        			  StringReader videoFeedReader = new StringReader(videoFeed); 
	        			  videoFeedReader.reset();
	        			  videos = parseYouTubeFeed(videoFeedReader);

	        		  }
	        		  catch (IOException e)
	        		  {
	        			  // TODO Auto-generated catch block
	        			  e.printStackTrace();
	        		  }

	        		  // notify the caller that the results are ready
	        		  // and send him a list of the videos!
	        		  {
	        			  Message videoFeedsMessage = new Message();
	        			  videoFeedsMessage.what = GoogleServiceCallbackType.YouTubeVideoFeedsReceived;

	        			  Bundle bundle = new Bundle();
	        			  bundle.putSerializable("VIDEOS", videos);

	        			  videoFeedsMessage.setData(bundle);
	        			  _ownerCallback.sendMessage(videoFeedsMessage);
	        		  }

	        		  break;
	        	  case GoogleServiceCallbackType.YouTubeVideoRatingPosted:
	        		  
	        		  // ok... rating response is not important...we can safely ignore it
	        		  // String videoRatingResponse = msg.getData().getString("RESPONSE");

	        		  _ownerCallback.sendEmptyMessage(GoogleServiceCallbackType.YouTubeVideoRatingPosted);
	        		  break;
	        		  
	        	  case GoogleServiceCallbackType.YouTubeVideoCommentsReceived:

	        		   // ok
	        		   String videoCommentsResponse = msg.getData().getString("RESPONSE");
	        		   ArrayList<YouTubeVideoComment> comments = null;
	        		   try
						  {
							  StringReader videoCommentsReader = new StringReader(videoCommentsResponse); 
							  videoCommentsReader.reset();
							  comments = parseVideoComments(videoCommentsReader);
						  }
						  catch (IOException e)
						  {
							// TODO Auto-generated catch block
							e.printStackTrace();
					 	  }
						  
						  // notify the caller that we have the comments and send them to him	
						  {
							  Message commentsMessage = new Message();
							  commentsMessage.what = GoogleServiceCallbackType.YouTubeVideoCommentsReceived;

							  Bundle bundle = new Bundle();
							  bundle.putSerializable("COMMENTS", comments);

							  commentsMessage.setData(bundle);
							  _ownerCallback.sendMessage(commentsMessage);
						  }

	        		  	break;

	        	  case GoogleServiceCallbackType.YouTubeVideoCommentPosted:
	        		  
	        		  // ok.. response can be safely ignored
	        		  // String videoCommentPostedResponse = msg.getData().getString("RESPONSE");
		        	  _ownerCallback.sendEmptyMessage(GoogleServiceCallbackType.YouTubeVideoCommentPosted);
		        	  
		        		break;
	        	  case GoogleServiceCallbackType.YouTubeVideoCommentReplyPosted:
	        		  
	        		   // ok.. response can be safely ignored
	        		   // TODO:: there is some problem with the implementation
	        		   // the comment is not posted as a reply, but as a new comment
	        		  
	        		   //String videoCommentReplyPostedResponse = msg.getData().getString("RESPONSE");
		        	  _ownerCallback.sendEmptyMessage(GoogleServiceCallbackType.YouTubeVideoCommentReplyPosted);
		        	  
   	        		   break;

   	        		   //
   	        		   // Video Uploading is a two step process
   	        		   //
	        	  case GoogleServiceCallbackType.YouTubeVideoMetadataUploaded:
	        		  
	        		   //
	        		   // step 1 completed . video metadata uploaded.
	        		   //
	        		   String videoMetadataUploadResponse = msg.getData().getString("RESPONSE");
	        		   if (videoMetadataUploadResponse.length() > 0)
	        		   {
	        			   // the metadata upload request returned an error..
	        			   // TODO:: find a better way to report the error
	        			   _ownerCallback.sendEmptyMessage(GoogleServiceCallbackType.OperationError);
	        		   }
	        		   
	        		   
	        		   Header[] videoMetadataUploadResponseheaders = (Header[]) msg.getData().getSerializable("HEADERS");
	        		   String uploadLocation="";
	        		   
	        		   //
	        		   // get the upload location in order to continue uploading the content
	        		   for (Header header:videoMetadataUploadResponseheaders)
	        		   {
	        			   if (header.getName().equalsIgnoreCase("Location"))
	        			   {
	        				   uploadLocation = header.getValue();	   
	        				   break;
	        			   }
	        		   }
	        				   
	        		   uploadVideoContent(uploadLocation);

		        	  break;
   	        			   
	        	  case  GoogleServiceCallbackType.YouTubeVideoContentUploaded:
	        		  
	        		  // ok...response can be safely ignored
	        		  // String videoContentUploadResponse = msg.getData().getString("RESPONSE");
	        		   _ownerCallback.sendEmptyMessage(GoogleServiceCallbackType.YouTubeVideoContentUploaded);
	        		  break;
		        		
	        	  case GoogleServiceCallbackType.YouTubeSubscriptionsReceived:
	        		  
	        		   String userSubscriptionsResponse = msg.getData().getString("RESPONSE");
	        		   ArrayList<YouTubeSubscription> subscriptions = null;
	        		   
	        		   try
						  {
	        			   
							  StringReader userSubscriptionsReader = new StringReader(userSubscriptionsResponse); 
							  userSubscriptionsReader.reset();
							  subscriptions = parseUserSubscriptions(userSubscriptionsReader);
						  }
						  catch (IOException e)
						  {
							// TODO Auto-generated catch block
							e.printStackTrace();
					 	  }
						 
						  getVideosforSubscription(subscriptions.get(0));
						  
						  _ownerCallback.sendEmptyMessage(GoogleServiceCallbackType.YouTubeSubscriptionsReceived);
 	        		     
						  {
							  Message subscriptionsMessage = new Message();
							  subscriptionsMessage.what = GoogleServiceCallbackType.YouTubeSubscriptionsReceived;

							  Bundle bundle = new Bundle();
							  bundle.putSerializable("SUBSCRIPTIONS", subscriptions);

							  subscriptionsMessage.setData(bundle);
							  _ownerCallback.sendMessage(subscriptionsMessage);
						  }
						  
	        		  break;

	        	  case GoogleServiceCallbackType.YouTubeSubscriptionsVideoFeedReceived:
	        		  
	        		  //TODO:: not completed yet....
	        		  String userSubscriptionsVideoFeedsResponse = msg.getData().getString("RESPONSE");
	        		  try
					  {
        			   
						  StringReader userSubscriptionsVideoFeedsReader = new StringReader(userSubscriptionsVideoFeedsResponse); 
						  userSubscriptionsVideoFeedsReader.reset();
						  //subscriptions = parseUserSubscriptions(userSubscriptionsReader);
					  }
					  catch (IOException e)
					  {
						// TODO Auto-generated catch block
						e.printStackTrace();
				 	  }
					  
	        			break;
	        	  case GoogleServiceCallbackType.OperationError:
	        		  _ownerCallback.sendMessage(msg);
	        		  break;
	          }
	      }
	  };

	 public YouTube(Activity activity,Handler ownerCallback)
	 {
		    _activity = activity;
		 	_ownerCallback = ownerCallback;
		 	
		    _manager = new GoogleAccountManager(_activity,_messageHandler);
	 }
	

	public void authenticate()
	 {
			//
			// Important note:
			// When you use the gmail account to access youtube (because it allows to use google credentials)
			// you have to link first the/a youtube account to the gmail account. then on the phone you should open 
			// the youtube application and add the account (use the gmail credentials)
			// After this procedure , the account manager can return an auth token for youtube 
			// (otherwise Account manager returns "NoLinkedYouTubeAccount" instead of a valid token) 
			// 

		    _manager.getYouTubeToken();
	 }

	 public void search(String searchTerms)
	 {
		  
		 try
		 {
			HttpConnector connector = new HttpConnector();

			String sessionUrl = "http://gdata.youtube.com/feeds/api/videos?q="+URLEncoder.encode(searchTerms, "UTF-8");
			
			Header[] headers = new Header[2];
			headers[0] = new BasicHeader("Authorization", "GoogleLogin auth="+_authToken);
			headers[1] = new BasicHeader("X-GData-Key", "key="+_developerKey);
			//
			// Important note:
			// When you use the gmail account to access youtube (because it allows to use google credentials)
			// you have to link first the/a youtube account to the gmail account. then on the phone you should open 
			// the youtube application and add the account (use the gmail credentials)
			// After this procedure , the account manager can return an auth token for youtube 
			// (otherwise Account manager returns "NoLinkedYouTubeAccount" instead of a valid token) 
			// 
			connector.executeGetRequestAsync(sessionUrl, GoogleServiceCallbackType.YouTubeVideoFeedsReceived,_messageHandler,headers);
		 }
		 catch (Exception e)
		 {
			 
		 }
			
	}

	 public void getComments(String commentsUrl)
	 {
		 try
		 {
			// TODO:: in order to get the rest pages...append to the commentsurl the following
			 // ?start-index=3&max-results=5
			 // adjust the 'max-results' depending on how many result are required and the 'start-index' depeding on the  countHint returned from the feed 
			HttpConnector connector = new HttpConnector();
			
			Header[] headers = new Header[2];
			headers[0] = new BasicHeader("Authorization", "GoogleLogin auth="+_authToken);
			headers[1] = new BasicHeader("X-GData-Key", "key="+_developerKey);
		
			connector.executeGetRequestAsync(commentsUrl, GoogleServiceCallbackType.YouTubeVideoCommentsReceived,_messageHandler,headers);
		 }
		 catch (Exception e)
		 {
			 
		 }
			
	 }
	 
	 public void submitComment(String commentsUrl, String comment)
	 {
		 try
		 {
			HttpConnector connector = new HttpConnector();
			
			Header[] headers = new Header[3];
			headers[0] = new BasicHeader("Authorization", "GoogleLogin auth="+_authToken);
			headers[1] = new BasicHeader("X-GData-Key", "key="+_developerKey);
			headers[2] = new BasicHeader("GData-Version", "2");

			String commentData = String.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?> <entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:yt=\"http://gdata.youtube.com/schemas/2007\"> <content>%s</content> </entry>",comment);

			StringEntity  postData = new StringEntity(commentData,"UTF-8");
			postData.setContentType("application/atom+xml");				
			
			connector.executePostRequestAsync(commentsUrl, GoogleServiceCallbackType.YouTubeVideoCommentPosted, postData, _messageHandler, headers);
		 }
		 catch (Exception e)
		 {
			 
		 }
	 }
	 
	 public void replyToComment(String commentsUrl, String replyToCommentUrl, String comment)
	 {
		 try
		 {
			// TODO:: this does not seems to work...it posts the comment in the video instead of replying to the comment
			HttpConnector connector = new HttpConnector();
			
			Header[] headers = new Header[3];
			headers[0] = new BasicHeader("Authorization", "GoogleLogin auth="+_authToken);
			headers[1] = new BasicHeader("X-GData-Key", "key="+_developerKey);
			headers[2] = new BasicHeader("GData-Version", "2");

			String commentData = String.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?> <entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:yt=\"http://gdata.youtube.com/schemas/2007\"> <link rel=\"http://gdata.youtube.com/schemas/2007#in-reply-to\" type=\"application/atom+xml\" href=\"%s\"/> <content>%s</content> </entry>",replyToCommentUrl,comment);

			StringEntity  postData = new StringEntity(commentData,"UTF-8");
			postData.setContentType("application/atom+xml");				
			
			connector.executePostRequestAsync(commentsUrl, GoogleServiceCallbackType.YouTubeVideoCommentReplyPosted, postData, _messageHandler, headers);
		 }
		 catch (Exception e)
		 {
			 
		 }
	 }
	 
	 public void getSubscriptions()
	 {
		 try
		 {
			HttpConnector connector = new HttpConnector();

			String subscriptionsUrl = "http://gdata.youtube.com/feeds/api/users/default/subscriptions?v=2";
			
			Header[] headers = new Header[2];
			headers[0] = new BasicHeader("Authorization", "GoogleLogin auth="+_authToken);
			headers[1] = new BasicHeader("X-GData-Key", "key="+_developerKey);
			
			connector.executeGetRequestAsync(subscriptionsUrl, GoogleServiceCallbackType.YouTubeSubscriptionsReceived,_messageHandler,headers);
		 }
		 catch (Exception e)
		 {
			 
		 }
	 }
	 
	 
	 public void getVideosforSubscription(YouTubeSubscription subscription)
	 {
		 try
		 {
			HttpConnector connector = new HttpConnector();

			String subscriptionVideoFeedUrl = subscription.getVideoFeedsUrl();//= "http://gdata.youtube.com/feeds/api/users/default/subscriptions?v=2";
			
			Header[] headers = new Header[2];
			headers[0] = new BasicHeader("Authorization", "GoogleLogin auth="+_authToken);
			headers[1] = new BasicHeader("X-GData-Key", "key="+_developerKey);
			
			connector.executeGetRequestAsync(subscriptionVideoFeedUrl, GoogleServiceCallbackType.YouTubeSubscriptionsVideoFeedReceived,_messageHandler,headers);
		 }
		 catch (Exception e)
		 {
			 
		 }
		 
	 }

	 public void getNewVideosforSubscription(YouTubeSubscription subscription)
	 {
		 
	 }

	 
	 public void subscribeToUserActivities(String userName)
	 {
		 
	 }
	 
	 public void subscribeToUserChannel(String userName)
	 {
		 
	 }
	 
	 public void subscribeToPlaylist(String playlistId)
	 {
		 
	 }
	 
	 public void unsubscribe(String subscriptionId)
	 {
		 
	 }
	 
	 public void rateAsLike(String rateUrl)
	 {
		 rateLikeOrDislike(rateUrl,"like");
	 }

	 public void rateAsDislike(String rateUrl)
	 {
		 rateLikeOrDislike(rateUrl,"dislike");
	 }
	 
	 private void rateLikeOrDislike(String rateUrl,String rating)
	 {
		 try
		 {
			HttpConnector connector = new HttpConnector();
			
			Header[] headers = new Header[3];
			headers[0] = new BasicHeader("Authorization", "GoogleLogin auth="+_authToken);
			headers[1] = new BasicHeader("X-GData-Key", "key="+_developerKey);
			headers[2] = new BasicHeader("GData-Version", "2");

			String ratingData = String.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?> <entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:yt=\"http://gdata.youtube.com/schemas/2007\"> <yt:rating value=\"%s\"/> </entry>",rating);

			StringEntity  postData = new StringEntity(ratingData,"UTF-8");
			postData.setContentType("application/atom+xml");				
			
			connector.executePostRequestAsync(rateUrl, GoogleServiceCallbackType.YouTubeVideoRatingPosted, postData, _messageHandler, headers);
		 }
		 catch (Exception e)
		 {
			 
		 }
	 }
	 
	 public void rate(String rateUrl,int rating)
	 {
		 if (rating>=1 && rating <=5)
		 {
			 try
			 {
				HttpConnector connector = new HttpConnector();
				
				Header[] headers = new Header[3];
				headers[0] = new BasicHeader("Authorization", "GoogleLogin auth="+_authToken);
				headers[1] = new BasicHeader("X-GData-Key", "key="+_developerKey);
				headers[2] = new BasicHeader("GData-Version", "2");

				String ratingData = String.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?> <entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:gd=\"http://schemas.google.com/g/2005\"> <gd:rating value=\"%d\" min=\"1\" max=\"5\"/> </entry>",rating);

				StringEntity  postData = new StringEntity(ratingData,"UTF-8");
				postData.setContentType("application/atom+xml");				
				
				connector.executePostRequestAsync(rateUrl, GoogleServiceCallbackType.YouTubeVideoRatingPosted, postData, _messageHandler, headers);
			 }
			 catch (Exception e)
			 {
				 
			 }
		 }
	 }
	 
	 public void complain(String complainUrl, ComplainType kind, String summary)
	 {
		 
		 try
		 {
			HttpConnector connector = new HttpConnector();
			
			Header[] headers = new Header[3];
			headers[0] = new BasicHeader("Authorization", "GoogleLogin auth="+_authToken);
			headers[1] = new BasicHeader("X-GData-Key", "key="+_developerKey);
			headers[2] = new BasicHeader("GData-Version", "2");
		
			String complainData = String.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?> <entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:yt=\"http://gdata.youtube.com/schemas/2007\"> <summary>%s</summary> <category scheme=\"http://gdata.youtube.com/schemas/2007/complaint-reasons.cat\" term=\"%s\"/> </entry>",summary,translateComplainType(kind));

			StringEntity  postData = new StringEntity(complainData,"UTF-8");
			postData.setContentType("application/atom+xml");				
			
			connector.executePostRequestAsync(complainUrl, GoogleServiceCallbackType.YouTubeVideoRatingPosted, postData, _messageHandler, headers);
		 }
		 catch (Exception e)
		 {
			 
		 }
	 }
	 
	 public void uploadVideo(String filename,String title,String description,String category,String[] keywords ) throws Exception
	 {
		 //
		 // video uploading is using the resumable procedure.
		 //
		 
		 // make up the video tags (which are mandatory)
		 StringBuilder tags = new StringBuilder();
		 
		 if (keywords == null)
			 throw new Exception("Keywords cannot be null or empty");	
		 
			 
		 for (String keyword : keywords)
		 {
			 tags.append(keyword);
			 tags.append(",");
		 }
		 
		 // save the filename for step 2 
		 _videoFilename = filename;
		 
		 //
		 // start by uploading the videos' metadata
		 uploadVideoMetadata(filename,title,description,category,tags.toString());
		 
	 }
	 
	 private void uploadVideoMetadata(String filename,String title,String description,String category,String keywords ) 
	 {
		 //TODO:: maybe we have to present the user with the categories...
		 // we can get them from here http://gdata.youtube.com/schemas/2007/categories.cat
		 try
		 {
			HttpConnector connector = new HttpConnector();
			
			Header[] headers = new Header[4];
			headers[0] = new BasicHeader("Authorization", "GoogleLogin auth="+_authToken);
			headers[1] = new BasicHeader("X-GData-Key", "key="+_developerKey);
			headers[2] = new BasicHeader("GData-Version", "2");
			headers[3] = new BasicHeader("Slug",filename);
			
			String videoMetadata = String.format("<?xml version=\"1.0\"?> <entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:media=\"http://search.yahoo.com/mrss/\" xmlns:yt=\"http://gdata.youtube.com/schemas/2007\"> <media:group> <media:title type=\"plain\"> %s </media:title> <media:description type=\"plain\"> %s </media:description> <media:category scheme=\"http://gdata.youtube.com/schemas/2007/categories.cat\"> %s </media:category> <media:keywords> %s </media:keywords> </media:group> </entry>",title,description,category,keywords);

			StringEntity  postData = new StringEntity(videoMetadata,"UTF-8");
			postData.setContentType("application/atom+xml; charset=UTF-8");				
			
			connector.executePostRequestAsync(_resumableUploadsUrl, GoogleServiceCallbackType.YouTubeVideoMetadataUploaded, postData, _messageHandler, headers);
		 }
		 catch (Exception e)
		 {
			 
		 }
	 }
	 	 	 
	 private void uploadVideoContent(String uploadLocation)
	 {
		 //
		 // step 1 is completed.(metadata uploaded)
		 //
		 
		 try
		 {
			HttpConnector connector = new HttpConnector();
			
			File    filetoUpload = new File(_videoFilename);
			
			FileEntity  fileData = new FileEntity(filetoUpload,"video/mp4"/*"application/octet-stream"*/);
		
			connector.executePutRequestAsync(uploadLocation, GoogleServiceCallbackType.YouTubeVideoContentUploaded, fileData, _messageHandler);
		 }
		 catch (Exception e)
		 {
			 
		 }
		 
	 }
	 
	 private String translateComplainType(ComplainType kind)
	 {
		 
		 switch (kind)
		 {
			 case Porn:
				 return "PORN";
			 case Hate:
		    	 return "HATE"; 
		     case Dangerous:
		    	 return "DANGEROUS"; 
		     case Rights:
		    	 return "RIGHTS";
		     case Spam:
		    	 return "SPAM";
		     case Violence:
		    	 return "VIOLENCE";
		    default:
		    	return "";
		 }
	 }
	 	 
	 private ArrayList<YouTubeVideoEntry> parseYouTubeFeed(Reader youTubeFeed)
	 {
		 ArrayList<YouTubeVideoEntry> videos = new ArrayList<YouTubeVideoEntry>();
		 
		 try
			{
			    
			    
				SAXReader reader = new SAXReader();
				Document feedsDocument = reader.read(youTubeFeed);
				
				Element rootFeed = (Element) feedsDocument.selectSingleNode ("/feed");

				
				// get the namespaces for later Xpath 
				String rootXmlNsURI =  rootFeed.getNamespaceURI();
				String gdNameSpace = rootFeed.getNamespaceForPrefix("gd").getURI();
				String mediaNameSpace = rootFeed.getNamespaceForPrefix("media").getURI();
				String openSearchNameSpace = rootFeed.getNamespaceForPrefix("openSearch").getURI();
				String ytNameSpace = rootFeed.getNamespaceForPrefix("yt").getURI();
				
				//String openSearchNameSpace = rootFeed.getNamespaceForPrefix("openSearch").getURI();
				
				feedsDocument.getRootElement().add(DocumentHelper.createNamespace("ns",rootXmlNsURI));
				feedsDocument.getRootElement().add(DocumentHelper.createNamespace("gd",gdNameSpace));
				feedsDocument.getRootElement().add(DocumentHelper.createNamespace("media",mediaNameSpace));
				feedsDocument.getRootElement().add(DocumentHelper.createNamespace("opensearch",openSearchNameSpace));
				feedsDocument.getRootElement().add(DocumentHelper.createNamespace("yt",ytNameSpace));
				
				String searchTitle = rootFeed.elementText("title");
				String totalResults = rootFeed.selectSingleNode("opensearch:totalResults").getStringValue();
				String startIndex = rootFeed.selectSingleNode("opensearch:startIndex").getStringValue();
				String itemsPerPage = rootFeed.selectSingleNode("opensearch:itemsPerPage").getStringValue();
				
				
				List<Element> feedNodes = feedsDocument.selectNodes("//ns:feed/ns:entry");
				for (Element feed:feedNodes)
				{
					try
					{
						videos.add(parseFeed(feed));
					}
					catch (NullPointerException e)
					{
						// ignore errors...and put as many videos as possible
					}
				}
				
			} catch (DocumentException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			return videos; 
	 }
	 
	 private YouTubeVideoEntry parseFeed(Element videoEntry) throws NullPointerException
	 {
		 YouTubeVideoEntry video = new YouTubeVideoEntry();

		 String videoId = videoEntry.elementText("id");
		 video.setVideoId(videoId);

		 String publishDate = videoEntry.elementText("published");
		 video.setPublishDate(publishDate);

		 String updateDate = videoEntry.elementText("updated");
		 video.setUpdateDate(updateDate);

		 String videoTitle = videoEntry.elementText("title");
		 video.setVideoTitle(videoTitle);

		 String authorName = videoEntry.selectSingleNode("ns:author/ns:name").getStringValue();
		 video.setAuthorName(authorName);

		 String authorPage = videoEntry.selectSingleNode("ns:author/ns:uri").getStringValue();
		 video.setAuthorPage(authorPage);

		 String commentsPage = ((Element)videoEntry.selectSingleNode("gd:comments/gd:feedLink")).attributeValue("href");
		 video.setCommentsUrl(commentsPage);

		 String commentsCounter = ((Element)videoEntry.selectSingleNode("gd:comments/gd:feedLink")).attributeValue("countHint");
		 video.setCommentsCounter(Integer.parseInt(commentsCounter));

		 String videoUrl= ((Element)videoEntry.selectSingleNode("media:group/media:player")).attributeValue("url");
		 video.setVideoUrl(videoUrl);

		 String videoDescription= videoEntry.selectSingleNode("media:group/media:description").getStringValue();
		 video.setVideoDescription(videoDescription);

		 String videoDuration= ((Element)videoEntry.selectSingleNode("media:group/yt:duration")).attributeValue("seconds");
		 video.setDuration(Integer.parseInt(videoDuration));

		 String ratingsUrl = ((Element)videoEntry.selectSingleNode("ns:link[@rel='http://gdata.youtube.com/schemas/2007#video.ratings']")).attributeValue("href");
		 video.setRatingsUrl(ratingsUrl);
		 
		 String complaintsUrl = ((Element)videoEntry.selectSingleNode("ns:link[@rel='http://gdata.youtube.com/schemas/2007#video.complaints']")).attributeValue("href");
		 video.setComplaintsUrl(complaintsUrl);
		 
		 String ratingAverage = ((Element)videoEntry.selectSingleNode("gd:rating")).attributeValue("average");
		 video.setRatingAverage(Double.parseDouble(ratingAverage));

		 String maxAverage = ((Element)videoEntry.selectSingleNode("gd:rating")).attributeValue("max");
		 video.setMaxAverage(Integer.parseInt(maxAverage));

		 String minAverage = ((Element)videoEntry.selectSingleNode("gd:rating")).attributeValue("min");
		 video.setMinAverage(Integer.parseInt(minAverage));

		 String numOfRatings= ((Element)videoEntry.selectSingleNode("gd:rating")).attributeValue("numRaters");
		 video.setNumOfRatings(Integer.parseInt(numOfRatings));

		 String viewCount = ((Element)videoEntry.selectSingleNode("yt:statistics")).attributeValue("viewCount");
		 video.setViewCount(Integer.parseInt(viewCount));

		 String favouriteCount= ((Element)videoEntry.selectSingleNode("yt:statistics")).attributeValue("favoriteCount");
		 video.setFavouriteCount(Integer.parseInt(favouriteCount));
		 
		 List<Element> thumbnailsNodes = videoEntry.selectNodes("media:group/media:thumbnail");
		 
		 for (Element thumbnail:thumbnailsNodes)
		 {
			 String thumbnailurl = thumbnail.attributeValue("url");
			 String timestamp  = thumbnail.attributeValue("time");
			 int width = Integer.parseInt(thumbnail.attributeValue("width"));
			 int height = Integer.parseInt(thumbnail.attributeValue("height"));
			 
			 video.addThumbnail(new YouTubeVideoThumbnail(thumbnailurl,timestamp,width,height));
		 }
		 

		 return video;

	 }
	 
  	 private ArrayList<YouTubeVideoComment>  parseVideoComments(StringReader videoCommentsReader)
	 {
  		 ArrayList<YouTubeVideoComment> comments = new ArrayList<YouTubeVideoComment>();
  		 
  		 try
  		 {
  			 SAXReader reader = new SAXReader();
  			 Document feedsDocument = reader.read(videoCommentsReader);

  			 Element rootFeed = (Element) feedsDocument.selectSingleNode ("/feed");

  			 // get the namespaces for later Xpath 
  			 String rootXmlNsURI =  rootFeed.getNamespaceURI();
  			 String openSearchNameSpace = rootFeed.getNamespaceForPrefix("openSearch").getURI();

  			 feedsDocument.getRootElement().add(DocumentHelper.createNamespace("ns",rootXmlNsURI));
  			 feedsDocument.getRootElement().add(DocumentHelper.createNamespace("opensearch",openSearchNameSpace));

  			 // this "id" is actualy the comments url of the video containing the comment
  			 String commentsUrl = rootFeed.elementText("id");
  			 String searchTitle = rootFeed.elementText("title");
  			 String totalResults = rootFeed.selectSingleNode("opensearch:totalResults").getStringValue();
  			 String startIndex = rootFeed.selectSingleNode("opensearch:startIndex").getStringValue();
  			 String itemsPerPage = rootFeed.selectSingleNode("opensearch:itemsPerPage").getStringValue();


  			 List<Element> commentNodes = feedsDocument.selectNodes("//ns:feed/ns:entry");
  			 for (Element commentElement:commentNodes)
  			 {
  				 try
  				 {
  					YouTubeVideoComment comment = parseComment(commentElement);
  				    // this is required, in case to know on which video the comment belongs
  					// and is used when someone need to reply to a comment
  					comment.setCommentsUrl(commentsUrl); 
  					comments.add(comment);
  				 }
  				 catch (NullPointerException e)
  				 {
  					 // ignore errors...and put as many comments as possible
  				 }
  			 }

  		 } catch (DocumentException e)
  		 {
  			 e.printStackTrace();
  		 }

  		 return comments;

	 }

	 private YouTubeVideoComment parseComment(Element commentEntry) throws NullPointerException
	 {
		 YouTubeVideoComment comment = new YouTubeVideoComment();
		 
		 String commentId = commentEntry.elementText("id");
		 comment.setCommentId(commentId);

		 String publishDate = commentEntry.elementText("published");
		 comment.setPublishDate(publishDate);

		 String updateDate = commentEntry.elementText("updated");
		 comment.setUpdateDate(updateDate);

		 String commentTitle = commentEntry.elementText("title");
		 comment.setCommentTitle(commentTitle);

		 String commentContent = commentEntry.elementText("content");
		 comment.setCommentContent(commentContent);

		 String authorName = commentEntry.selectSingleNode("ns:author/ns:name").getStringValue();
		 comment.setAuthorName(authorName);

		 String authorPage = commentEntry.selectSingleNode("ns:author/ns:uri").getStringValue();
		 comment.setAuthorPage(authorPage);

		 String replyToUrl = ((Element)commentEntry.selectSingleNode("ns:link[@rel='self']")).attributeValue("href");
		 comment.setReplyToUrl(replyToUrl);

		 return comment;
	 }

	 private ArrayList<YouTubeSubscription> parseUserSubscriptions(StringReader userSubscriptionsReader)
	 {
		 ArrayList<YouTubeSubscription> subscriptions = new ArrayList<YouTubeSubscription>();

  		 try
  		 {
  			 SAXReader reader = new SAXReader();
  			 Document subscriptionsDocument = reader.read(userSubscriptionsReader);

  			 Element rootFeed = (Element) subscriptionsDocument.selectSingleNode ("/feed");
  			 
  			 // get the namespaces for later Xpath 
  			 String rootXmlNsURI =  rootFeed.getNamespaceURI();
  			 String gdNameSpace = rootFeed.getNamespaceForPrefix("gd").getURI();
  			 String mediaNameSpace = rootFeed.getNamespaceForPrefix("media").getURI();
  			 String ytNameSpace = rootFeed.getNamespaceForPrefix("yt").getURI();
  			 String openSearchNameSpace = rootFeed.getNamespaceForPrefix("openSearch").getURI();


  			 subscriptionsDocument.getRootElement().add(DocumentHelper.createNamespace("ns",rootXmlNsURI));
  			 subscriptionsDocument.getRootElement().add(DocumentHelper.createNamespace("gd",gdNameSpace));
  			 subscriptionsDocument.getRootElement().add(DocumentHelper.createNamespace("media",mediaNameSpace));
  			 subscriptionsDocument.getRootElement().add(DocumentHelper.createNamespace("opensearch",openSearchNameSpace));
  			 subscriptionsDocument.getRootElement().add(DocumentHelper.createNamespace("yt",ytNameSpace));
  			 
  			 String title = rootFeed.elementText("title");
  			 String totalResults = rootFeed.selectSingleNode("opensearch:totalResults").getStringValue();
  			 String startIndex = rootFeed.selectSingleNode("opensearch:startIndex").getStringValue();
  			 String itemsPerPage = rootFeed.selectSingleNode("opensearch:itemsPerPage").getStringValue();


  			 List<Element> subscriptionNodes = subscriptionsDocument.selectNodes("//ns:feed/ns:entry");
  			 
  			 for (Element subscriptionElement:subscriptionNodes)
  			 {
  				 try
  				 {
  					 YouTubeSubscription sub = parseSubscription(subscriptionElement); 
  					 subscriptions.add(sub);
  				 }
  				 catch (Exception e)
  				 {
  					 // ignore errors...and put as many comments as possible
  				 }
  			 }

  		 } catch (DocumentException e)
  		 {
  			 e.printStackTrace();
  		 }

		 return subscriptions;
	 }


	private YouTubeSubscription parseSubscription(Element subscriptionElement) throws Exception
	{
		String subscriptionType = ((Element)subscriptionElement.selectSingleNode("ns:category[@scheme='http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat']")).attributeValue("term");
		
		YouTubeSubscription subscription;
		
		if (subscriptionType.equalsIgnoreCase("user"))
			subscription = new YouTubeUserActivitiesSubscription();
		else if (subscriptionType.equalsIgnoreCase("channel"))
			subscription = new YouTubeChannelSubscription();
		else if (subscriptionType.equalsIgnoreCase("playlist"))
			subscription = new YouTubePlaylistSubscription();
		else
			throw new Exception("Unsupported type of subscription");

		
		 // note: this is not "our" name...it is the name of the user that owns the channel/playlist/activity
		 String subscriptionUser =  ((Element)subscriptionElement.selectSingleNode("yt:username")).getText();//subscriptionElement.elementText("yt:username");
		 subscription.setUser(subscriptionUser);
		 
		 Element mediaThumbnail = (Element)subscriptionElement.selectSingleNode("media:thumbnail");
		 if (mediaThumbnail!=null)
		 {
			 String userThumbNailUrl = mediaThumbnail.attributeValue("url");
			 subscription.setUserThumbnailUrl(userThumbNailUrl);
		 }
		 
		 String removalUrl = ((Element)subscriptionElement.selectSingleNode("ns:link[@rel='edit']")).attributeValue("href"); 
		 subscription.setRemocalUrl(removalUrl);
		 
		 String videoFeedsUrl = ((Element)subscriptionElement.selectSingleNode("ns:content")).attributeValue("src");
		 subscription.setVideoFeedsUrl(videoFeedsUrl);
		 
		 String lastUpdate= subscriptionElement.elementText("updated");
		 subscription.setLastUpdate(lastUpdate);
		 
		 if (subscription.getSubscriptionType() == SubscriptionType.Playlist)
		 {
			 // playlist has two more elements we need
			 YouTubePlaylistSubscription playlistSubscription = (YouTubePlaylistSubscription)subscription;
			 
			 String playlistTitle = subscriptionElement.elementText("yt:playlistTitle");
			 playlistSubscription.setPlaylistTitle(playlistTitle);
			 
			 String playlistId = subscriptionElement.elementText("yt:playlistId");
			 playlistSubscription.setPlaylistId(playlistId);
			 
			 subscription = playlistSubscription;
		 }
		 
		 return subscription;
	}

}
